/*
 * Copyright 2007 National Bioinformatics Network
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package dgbgui.actions;

import java.util.HashSet;
import java.util.Observable;
import java.util.Observer;
import java.util.Set;

import dgbgui.actions.ActionRepository.ActionGroupName;
import dgbgui.actions.ActionRepository.ActionName;
import dgbgui.command.CommandStack;
import dgbgui.modeldocument.ModelDocument;
import dgbgui.modeldocument.ModelDrivenWorkspace;
import dgbgui.modeldocument.Query;
import dgbgui.modeldocument.ResultStore;
import dgbgui.modeldocument.definition.ClosureDefinition;
import dgbgui.modeldocument.definition.ConceptTypesDefinition;
import dgbgui.modeldocument.definition.CustomDefinition;
import dgbgui.modeldocument.definition.FilteredInstanceDefinition;
import dgbgui.modeldocument.definition.NeighbourhoodDefinition;
import dgbgui.modeldocument.primitives.Concept;
import dgbgui.modeldocument.primitives.Conceptron;
import dgbgui.modeldocument.primitives.ConceptronType;
import dgbgui.selection.SelectionManager;

/**
 * @author bjnortier
 * The ActionEnabler is responsible for enabling/disabling actions. It observer all the 
 * possible sources of action enabling/disabled, including selection, the model document etc.
 */
/**
 * @author bjnortier
 *
 */
/**
 * @author bjnortier
 *
 */
public class ActionEnabler implements Observer {
    
    /**
     * Disable all actions (when a tool is active)
     */
    public void disableAll() {
        // Disable all actions
        for ( ActionName actionName : ActionName.values() ) {
            m_repo.getAction(actionName).setEnabled(false);
        }
    }
    
    /**
     * Re-eable individual actions according to current application state
     */
    public void reenableFromCurrentState() {
        // Enable all first
        for ( ActionName actionName : ActionName.values() ) {
            m_repo.getAction(actionName).setEnabled(true);
        }
        updateFromSelection();
        updateFromModelDocument();
        updateFromCommandStack();
    }
    
    /**
     * Disable all actions that are selection dependant. By default, this is
     * all actions, so excluded actions must be added here.
     */
    private void disableSelectionDependentActions() {
        // Create a list of actions that are not selection dependent
        Set<ActionName> indepActions = new HashSet<ActionName>();
        indepActions.add(ActionName.FILE_NEW);
        indepActions.add(ActionName.FILE_OPEN);
        indepActions.add(ActionName.FILE_SAVE);
        indepActions.add(ActionName.FILE_SAVE_AS);
        indepActions.add(ActionName.EDIT_UNDO);
        indepActions.add(ActionName.EDIT_REDO);
        indepActions.add(ActionName.EDIT_PASTE);
        indepActions.add(ActionName.DGB_SETTINGS);
        indepActions.add(ActionName.QUERY_ADD_TYPES);
        indepActions.add(ActionName.DGB_GET_IOR_VIA_HTTP);
        indepActions.add(ActionName.QUERY_CREATE_CUSTOM);
        indepActions.add(ActionName.PERSPECTIVE_DEFAULT);
        indepActions.add(ActionName.PERSPECTIVE_MODEL);

        // Disable all actions not in the exclusion list
        for ( ActionName actionName : ActionName.values() ) {
            if ( !indepActions.contains(actionName) ) {
                m_repo.getAction(actionName).setEnabled(false);
            }
        }
    }
    
    /**
     * Enable/Disable actions according to the current selection state
     */
    private void updateFromSelection() {
        
        // Enable all the actions that aren't selection dependant, and
        // disable the rest
        disableSelectionDependentActions();
        // For convenience
        int numQueryNodesSelected = SelectionManager.getInstance().size(Query.class);
        int numConceptronTypesSelected = SelectionManager.getInstance().size(ConceptronType.class);
        int numConceptronInstancesSelected = SelectionManager.getInstance().size(Conceptron.class);
                        
        // -------------------- QUERY NODE ACTIONS -------------------
        
        // Undo and redo are handled by the command stack, so no need to activate
        // here
        if ( numQueryNodesSelected > 0 ) {
            m_repo.getAction(ActionName.EDIT_CUT).setEnabled(true);
            m_repo.getAction(ActionName.EDIT_COPY).setEnabled(true);
            m_repo.getAction(ActionName.EDIT_DELETE).setEnabled(true);
        }
        if ( numQueryNodesSelected == 1 ) {
            m_repo.getAction(ActionName.EDIT_RENAME).setEnabled(true);
        }
        if ( numQueryNodesSelected == 1) {
            if ( ( SelectionManager.getInstance().getDefinitionFilteredSelection(FilteredInstanceDefinition.class).size() == 1 ) ||
                 ( SelectionManager.getInstance().getDefinitionFilteredSelection(NeighbourhoodDefinition.class).size() == 1 ) || 
                 ( SelectionManager.getInstance().getDefinitionFilteredSelection(ClosureDefinition.class).size() == 1 ) ||
                 ( SelectionManager.getInstance().getDefinitionFilteredSelection(CustomDefinition.class).size() == 1 ) ) {
                m_repo.getAction(ActionName.EDIT_MODIFY).setEnabled(true);
            }
        }
        
        // -------------------- QUERY NODE ACTIONS -------------------
        
        /// To create a type query from a list of types, there must be a result
        if ( ( numQueryNodesSelected > 0) && 
                ( SelectionManager.getInstance().getDefinitionFilteredSelection(ConceptTypesDefinition.class).size() == numQueryNodesSelected) ) {
            int withResults = 0;
            for ( Object node : SelectionManager.getInstance().getSelection(Query.class) ) {
                if ( ResultStore.getInstance().hasResult((Query)node)) {
                    ++withResults;
                }
            }
        }
        
        if ( numConceptronTypesSelected > 0)  {
            m_repo.getAction(ActionName.QUERY_CREATE_SET).setEnabled(true);    
        }
        

        // For new queries from results, one or more concept types or instances must be selected
        int numConceptronTypesAndInstancesSelection = 
            numConceptronTypesSelected +
            numConceptronInstancesSelected;
        if ( numConceptronTypesAndInstancesSelection > 0 ) {
            m_repo.getAction(ActionName.QUERY_CREATE_FROM_RESULT).setEnabled(true);
        }
        
        // If something is selected, disable custom query creation
        if ( SelectionManager.getInstance().size() == 0 ) {
            m_repo.getAction(ActionName.QUERY_CREATE_CUSTOM).setEnabled(true);
        } else {
            m_repo.getAction(ActionName.QUERY_CREATE_CUSTOM).setEnabled(false);
        }
        
        // If something is selected, enable neighbourhood creation
        if ( numQueryNodesSelected == 1 ) {
            m_repo.getAction(ActionName.QUERY_CREATE_NEIGHBOURHOOD).setEnabled(true);
        }
        
        // If something is selected, enable closure creation
        if ( numQueryNodesSelected == 1 ) {
            m_repo.getAction(ActionName.QUERY_CREATE_CLOSURE).setEnabled(true);
        }
        
        
        // Execute queries. 
        // TODO: Add validation to only enable if query nodes do not have results
        if ( numQueryNodesSelected >= 1) {
            m_repo.getAction(ActionName.QUERY_EXECUTE).setEnabled(true);
        }
        
        
        
        // -------------------- BOOLEAN ACTIONS -------------------
        
        // For boolean actions, all the query nodes must have a set result 
        // type.
        if ( ( numQueryNodesSelected > 1) ) {
            m_repo.getActionGroup(ActionGroupName.BOOLEAN).setEnabled(true);
        }
        // But NOT must be only 1
        if ( ( numQueryNodesSelected == 1) ) {
            m_repo.getAction(ActionName.BOOLEAN_NOT).setEnabled(true);
        }
            
        // -------------------- MODEL_DRIVEN ACTIONS -------------------
        
        // For setting the target, the currently selected concept 
        // must be in the model driven workspace. The target must not be set
        // already
        if ( numConceptronInstancesSelected == 1 ) {
            Set<Object> selection = 
                SelectionManager.getInstance().getSelection(Conceptron.class);
            Conceptron conceptron = (Conceptron)selection.iterator().next();
            if ( ( conceptron instanceof Concept ) && 
                 conceptron.isScemataIdentifier() &&
                 (ModelDrivenWorkspace.getInstance().getTarget() == null) ) {
                m_repo.getAction(ActionName.MODEL_SET_TARGET).setEnabled(true);
            }
        }
    }
    

    /**
     * Enable/Disable actions from the model document
     */
    private void updateFromModelDocument() {
        
        ActionRepository.getInstance().getAction(ActionRepository.ActionName.EDIT_PASTE)
            .setEnabled(ModelDocument.getInstance().getClipBoard().size() > 0);
    }
    
    /**
     * Enable/Disable actions according to the command stack
     */
    private void updateFromCommandStack() {
        // Undo/Redo depends on the availability of undo/redo commands
        ActionRepository.getInstance().getAction(ActionRepository.ActionName.EDIT_UNDO)
            .setEnabled(CommandStack.getInstance().getUndoStackSize() > 0);
        ActionRepository.getInstance().getAction(ActionRepository.ActionName.EDIT_REDO)
            .setEnabled(CommandStack.getInstance().getRedoStackSize() > 0);
        
        // File->Save
        ActionRepository.getInstance().getAction(ActionRepository.ActionName.FILE_SAVE)
            .setEnabled((CommandStack.getInstance().getUndoStackSize() > 0) && (!CommandStack.getInstance().atSavePoint()));
        // File->SaveAs
        ActionRepository.getInstance().getAction(ActionRepository.ActionName.FILE_SAVE_AS)
            .setEnabled(CommandStack.getInstance().getUndoStackSize() > 0);
    }
    
    /**
     * Observer the selection manager to validate the actions
     * @param source The source of the observed event
     * @param data The observed data
     */
    public void update(Observable source, Object data) {
        if ( source == SelectionManager.getInstance() ) {
            updateFromSelection();
        }
        if ( source instanceof ModelDocument ) {
            updateFromModelDocument();
        }
        if ( source instanceof CommandStack ) {
            updateFromCommandStack();
        }
    }
    
    
    // ------- Singleton ------
    
    public static ActionEnabler getInstance() {
        if ( m_instance == null ) {
            m_instance = new ActionEnabler();
        }
        return m_instance;
    }
    
    private static ActionEnabler m_instance = null;
    
    // Hide for the singleton
    private ActionEnabler() {
        disableAll();
        reenableFromCurrentState();
        
    }
    
    // --------------- Fields -----------
    
    ActionRepository m_repo = ActionRepository.getInstance();

}
